//
// winter - Direct2D GUI framework.
// Copyright (C) 2009 Aleksandar Dezelin 
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#ifndef COM_SCOPED_PTR_H
#define COM_SCOPED_PTR_H

#pragma once
#include "winter_assert.h"
#include <guiddef.h>
#include <string>

namespace winter {
namespace platform {
namespace com {

template<class T, const IID* piid = &__uuidof(T)> 
class com_scoped_ptr {
private:
  T* _px;

  com_scoped_ptr(const com_scoped_ptr&);
  com_scoped_ptr& operator=(const com_scoped_ptr &);

  bool operator==(const com_scoped_ptr&) const;
  bool operator!=(const com_scoped_ptr&) const;

  typedef com_scoped_ptr<T, piid> this_type;

public:
  typedef T element_type;

  com_scoped_ptr(T* p = 0) : _px(p) {
    if(_px)
      _px->AddRef();
  }

  ~com_scoped_ptr() {
    if(_px)
      _px->Release();
  }

  void reset(T* p = 0) {
    WINTER_ASSERT(p == 0 || p != _px); // catch self-reset errors
    this_type(p).swap(*this);
  }

  T* get() const {
    return _px;
  }

  T& operator*() const {
    WINTER_ASSERT(_px != 0);
    return *_px;
  }

  T* operator->() const {
    WINTER_ASSERT(_px != 0);
    return _px;
  }

  T** operator&() {
    return &_px;
  }

  operator bool() const {
    return _px != 0;
  }

  void swap(com_scoped_ptr& b) {
    T* tmp = b._px;
    b._px = _px;
    _px = tmp;
  }

  HRESULT co_create(const std::wstring& prog_id, IUnknown* outer = 0, DWORD context = CLSCTX_ALL) {
    IID clsid;
    HRESULT hr = ::CLSIDFromProgID(prog_id.c_str(), &clsid);
    if(SUCCEEDED(hr))
      hr = co_create(clsid, outer, context);

    return hr;
  }

  HRESULT co_create(const IID& clsid, IUnknown* outer = 0, DWORD context = CLSCTX_ALL) {
    reset();
    return ::CoCreateInstance(clsid, outer, context, *piid, reinterpret_cast<void**>(&_px));
  }
};

template<class T> 
inline void swap(com_scoped_ptr<T>& a, com_scoped_ptr<T>& b) {
  a.swap(b);
}

// get_pointer(p) is a generic way to say p.get()
template<class T> 
inline T* get_pointer(com_scoped_ptr<T> const& p) {
  return p.get();
}

}
}
}

#endif // COM_SCOPED_PTR_H
